// 获取线段与三个基准面的夹角（角度制）
import { MathTool } from '@amcax/base';
import { EntityInfo, EntityPartType, THREE } from '@amcax/web-render';
import { CSLine } from '../basic/CSLine';
import { GeometryItem } from '../types';
import { nmAppErrorLog } from './logUtils';

export function getLinePlaneAngles(
  startPoint,
  endPoint,
  axis: 'XY' | 'ZX' | 'YZ' = 'XY', // 默认基准面为XY
) {
  const direction = new THREE.Vector3().subVectors(endPoint, startPoint);

  // 方向向量不能为零向量
  if (direction.length() === 0) {
    throw new Error('线段两个端点重合，方向向量长度为零');
  }
  // 定义各平面法线（单位向量）
  const normals = {
    XY: new THREE.Vector3(0, 0, 1),
    ZX: new THREE.Vector3(0, 1, 0),
    YZ: new THREE.Vector3(1, 0, 0),
  }[axis];
  // 计算方向向量与法线夹角（弧度）
  const theta = direction.angleTo(normals);
  // 转换为与平面夹角（度数）
  const alpha = 90 - THREE.MathUtils.radToDeg(theta);
  // angles[plane] = Math.abs(alpha); // 确保角度在0~90°
  return {
    [axis]: MathTool.fixNumber(alpha, 2), // 转换为角度;
  };
}

// 创建基准面网格（半透明）辅助观察
export const createPlane = (size, color) => {
  const geometry = new THREE.PlaneGeometry(size, size);
  const material = new THREE.MeshBasicMaterial({
    color: color,
    side: THREE.DoubleSide,
    transparent: true,
    depthTest: false,
    opacity: 0.3,
  });
  const plane = new THREE.Mesh(geometry, material);
  plane.renderOrder = 2;
  return plane;
};

type LineParam = { start: THREE.Vector3; end: THREE.Vector3 };
/**
 * 创建一个表示两条线段夹角的圆弧网格对象。
 *
 * @param lineA - 第一条线段，需包含 `start` 和 `end` 属性，代表线段的起点和终点。
 * @param lineB - 第二条线段，需包含 `start` 和 `end` 属性，代表线段的起点和终点。
 * @param options - 可选配置对象，用于设置圆弧的相关参数。
 * @param options.radius - 圆弧的半径，默认为 100。
 * @param options.divisions - 圆弧的细分点数，用于控制圆弧的平滑度，默认为 50。
 * @returns 返回一个包含圆弧网格对象和圆弧点数组的对象；如果线段未在端点连接，则返回 `null`。
 */
export function createAngleArc(
  lineA: LineParam,
  lineB: LineParam,
  options: {
    radius?: number; // 圆弧半径
    divisions?: number; // 圆弧细分点数
  } = {},
) {
  // 参数处理
  const cfg = {
    radius: options.radius || 100,
    divisions: options.divisions || 50,
  };

  // 验证是否为相连线段
  if (!lineA.start.equals(lineB.start) && !lineA.end.equals(lineB.start)) {
    console.error('线段未在端点连接!');
    return null;
  }

  // 确定公共顶点和方向向量
  const vertex = lineA.start.clone();
  const dirA = lineA.end.clone().sub(vertex).normalize();
  const dirB = lineB.end.clone().sub(vertex).normalize();

  // 计算夹角（弧度）
  const angle = dirA.angleTo(dirB);

  // 计算旋转轴（平面法线）
  const rotationAxis = new THREE.Vector3().crossVectors(dirA, dirB).normalize();

  // 创建圆弧几何
  const arcPoints = [lineB.start.toArray()];
  const divisions = cfg.divisions;
  for (let i = 0; i <= divisions; i++) {
    const theta = (angle * i) / divisions;
    const point = new THREE.Vector3()
      .copy(dirA)
      .applyAxisAngle(rotationAxis, theta)
      .multiplyScalar(cfg.radius)
      .add(vertex);
    arcPoints.push(point.toArray());
  }

  const { geometry, material } = createSector(arcPoints);
  return {
    mesh: new THREE.Mesh(geometry, material),
    arcPoints,
    angle: THREE.MathUtils.radToDeg(angle), // 转换为角度
  };
}
/**
 * 构建几何体
 * 构建面索引（三角形扇）
 * 创建材质
 * */
export function createSector(
  points: Array<THREE.Vector3Tuple>,
  materials: {
    color?: number;
    depthTest?: boolean;
    opacity?: number;
    transparent?: boolean;
  } = {},
) {
  // 构建几何体
  const geometry = new THREE.BufferGeometry();
  const positions = new Float32Array(points.flat());
  geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

  // 构建面索引（三角形扇）
  const indices = [];
  for (let i = 1; i < points.length - 1; i++) {
    indices.push(0, i, i + 1);
  }
  geometry.setIndex(indices);
  geometry.computeVertexNormals();

  // 创建材质
  const material = new THREE.MeshPhongMaterial({
    color: 0xf75ef7,
    transparent: true,
    opacity: 0.5,
    depthTest: false,
    side: THREE.DoubleSide,
    ...materials,
  });
  return {
    geometry,
    material,
  };
}

/**
 * 根据原点和法向量创建坐标系
 * @param origin 原点坐标（Vector3）
 * @param normal 法向量（Vector3，无需归一化）
 * @returns 包含位置和基向量的坐标系数据
 */
export function createCoordinateSystem(
  origin: THREE.Vector3,
  normal: THREE.Vector3,
) {
  // 归一化法向量作为 Z 轴
  const zAxis: THREE.Vector3 = normal.clone().normalize();

  // 初始参考方向（世界 Y 轴）
  const reference = new THREE.Vector3(0, 1, 0);
  const xAxis = new THREE.Vector3();

  // 计算 X 轴：参考方向 × Z 轴
  xAxis.crossVectors(reference, zAxis);

  // 若叉乘结果为零向量（参考方向与 Z 轴平行）
  if (xAxis.length() < 0.0001) {
    // 改用世界 X 轴作为参考方向
    reference.set(1, 0, 0);
    xAxis.crossVectors(reference, zAxis);
  }

  xAxis.normalize();

  // 计算 Y 轴：Z 轴 × X 轴
  const yAxis = new THREE.Vector3().crossVectors(zAxis, xAxis).normalize();
  // 构建世界到局部的变换矩阵
  const worldToLocal = new THREE.Matrix4()
    .makeBasis(xAxis, yAxis, zAxis)
    .setPosition(origin)
    .invert();

  // 构建局部到世界的变换矩阵（逆矩阵）
  const localToWorld = new THREE.Matrix4()
    .makeBasis(xAxis, yAxis, zAxis)
    .setPosition(origin);
  return {
    origin: origin,
    x: xAxis,
    y: yAxis,
    z: zAxis,
    matrix: localToWorld,
    worldToLocal,
  };
}

/**
 * @description 计算点与多边形最近的两个点
 * @param point 点 (即鼠标的位置)
 * @param vertices 多边形的顶点
 * */
export function getCurveLastDistPoint(
  point: THREE.Vector3,
  vertices: THREE.Vector3[],
): { p: [THREE.Vector3, THREE.Vector3]; ind: number[] } {
  const distanceList = [];
  for (let i = 0; i < vertices.length; i++) {
    distanceList.push(vertices[i].distanceTo(point));
  }
  const min = Math.min(...distanceList);
  const index = distanceList.indexOf(min);
  let nextIndex = null;
  if (distanceList.length === index + 1) {
    nextIndex = distanceList[index - 1] > distanceList[0] ? 0 : index - 1;
  } else if (index === 0) {
    nextIndex =
      distanceList[1] > distanceList[distanceList.length - 1]
        ? distanceList.length - 1
        : 1;
  } else {
    nextIndex =
      distanceList[index - 1] > distanceList[index + 1] ? index + 1 : index - 1;
  }
  const ind = [index, nextIndex].sort((a, b) => a - b);
  return {
    p: ind.map((i) => vertices[i]) as [THREE.Vector3, THREE.Vector3],
    ind,
  };
}

/**
 * @description 根据向量、长度、点计算另一个点
 * @param vector 向量
 * @param length 长度
 * @param point 点
 * */
export function getPointByVecLen(
  vector: THREE.Vector3,
  length: number,
  point: THREE.Vector3,
): THREE.Vector3 {
  // 假设P是一个Three.js Vector3对象，表示点P
  // length是你想要的线段PQ的长度
  // 创建一个新的向量V，它是任意方向的（例如，从P到Q的方向）
  // 规范化向量V，使其长度为1
  vector.normalize();
  // 计算新的点Q，它是点P在向量V方向上长度为length的投影
  return point.clone().add(vector.clone().setLength(length));
}

/**
 * 根据折线点数组和比例，获取对应位置的坐标
 * @param {THREE.Vector3[]} points - 连续的点数组
 * @param {number} ratio - 比例 (0~1)
 * @returns {THREE.Vector3} 目标点的位置
 */
type Mode = 'ratio' | 'distance';
// 根据百分比计算点位
export function getPointByRatio(
  points: THREE.Vector3[],
  value: number,
  mode: Mode = 'ratio',
) {
  // const curve = new CatmullRomCurve3(points);
  // curve.getPointAt(ratio);
  if (points.length < 2) throw new Error('至少需要两个点');
  const { totalLength, segmentLengths } = getLineLen(points); // 1. 计算总长度
  const ratio = mode === 'ratio' ? value : value / totalLength; // 2. 计算比例
  if (mode === 'ratio') {
    // 检查比例是否小于等于 0 或大于等于 1
    if (value <= 0 || value >= 1) {
      // 比例小于等于 0，返回第一个点；比例大于等于 1，返回最后一个点
      return [points[0].clone(), points[points.length - 1].clone()];
    }
  } else if (value < 0 || value > totalLength) {
    return [points[0].clone(), points[points.length - 1].clone()];
  }
  // 判断比例和补值是否相等，如果相等则只使用一个比例，否则使用两个比例
  const ratios = ratio === 0.5 ? [ratio] : [ratio, 1 - ratio];
  // 2. 转换比例为排序后的目标长度（优化遍历效率）
  const targets = ratios
    .map((ratio, index) => ({
      ratio, // 当前比例
      targetLength: ratio * totalLength, // 目标长度，即总长度乘以当前比例
      originalIndex: index, // 原始索引
    }))
    .sort((a, b) => a.targetLength - b.targetLength);

  // --------------
  const result = new Array(ratios.length); // 3. 初始化结果数组
  let accumulatedLength = 0; // 累计长度
  let currentTargetIndex = 0; // 当前目标索引
  // 4. 遍历线段，处理所有目标点
  for (let i = 0; i < segmentLengths.length; i++) {
    const segmentLength = segmentLengths[i]; // 当前线段的长度
    const segmentStart = points[i]; // 当前线段的起点
    const segmentEnd = points[i + 1]; // 当前线段的终点

    // 检查当前线段是否覆盖未处理的目标点
    while (currentTargetIndex < targets.length) {
      const target = targets[currentTargetIndex]; // 当前目标
      // 如果目标长度大于累计长度加上当前线段长度，则跳出循环
      if (target.targetLength > accumulatedLength + segmentLength) break;
      const remaining = target.targetLength - accumulatedLength; // 计算该线段上的插值比例
      const t = remaining / segmentLength;
      const point = segmentStart.clone().lerp(segmentEnd, t); // 计算目标点
      result[target.originalIndex] = point; // 按原始顺序存储结果
      currentTargetIndex++; // 移动到下一个目标索引
    }
    accumulatedLength += segmentLength; // 累加当前线段长度到累计长度
    if (currentTargetIndex >= targets.length) break; // 如果所有目标点都已处理，提前退出循环
  }
  // 处理超出总长度的点（理论上不会执行）
  for (; currentTargetIndex < targets.length; currentTargetIndex++) {
    const target = targets[currentTargetIndex];
    // 超出总长度的点，返回最后一个点
    result[target.originalIndex] = points[points.length - 1].clone();
  }
  return result; // 返回结果数组
}

/**
 * 计算一组点所构成的折线的总长度、指定索引位置的累计长度以及每段线段的长度
 * @param points 一组连续的三维向量点，代表折线的各个顶点
 * @param index 可选参数，指定计算到哪一段线段结束时的累计长度
 * @returns 包含总长度、指定索引位置的累计长度以及每段线段长度的对象
 */
export function getLineLen(points: THREE.Vector3[], index?: number) {
  let totalLength = 0; // 初始化折线的总长度为 0
  let lenByIndex = 0; // 初始化指定索引位置的累计长度为 0
  const segmentLengths = []; // 用于存储每段线段的长度
  // 遍历所有点，计算相邻两点之间的距离
  for (let i = 0; i < points.length - 1; i++) {
    // 计算当前点到下一个点的距离
    const length = points[i].distanceTo(points[i + 1]);
    segmentLengths.push(length); // 将当前线段的长度添加到 segmentLengths 数组中
    totalLength += length; // 累加总长度
    // 如果指定了 index 且当前索引等于 index - 1，则记录当前的累计长度
    if (index && i === index - 1) {
      lenByIndex = totalLength;
    }
  }
  // 返回包含总长度、指定索引位置的累计长度以及每段线段长度的对象
  return {
    totalLength,
    lenByIndex,
    segmentLengths,
  };
}
/**
 * 将三维的点转化成二维的点
 * */
export const change2D = (
  worldPoint: THREE.Vector3,
  worldToLocal: THREE.Matrix4,
) => {
  // 将点转换到局部坐标系
  const localPoint = worldPoint.clone().applyMatrix4(worldToLocal);
  return new THREE.Vector2(localPoint.x, localPoint.y);
};
export const change2DDirection = (
  vector: THREE.Vector3, // Input can be a point or direction
  worldToLocal: THREE.Matrix4, // Transformation matrix
) => {
  const localVector = vector.clone();

  // For directions, apply only the rotation/scaling part
  localVector.transformDirection(worldToLocal).normalize();

  return new THREE.Vector2(localVector.x, localVector.y);
};
/**
 * 将二维的点转化成三维的点
 * */
export const change3D = (
  localXY: THREE.Vector2 | { x: number; y: number },
  localToworld: THREE.Matrix4,
) => {
  const localPoint = new THREE.Vector3(localXY.x, localXY.y, 0);
  return localPoint.applyMatrix4(localToworld);
};
/**
 * 计算鼠标在指定平面上的交点。
 *
 * @param mouse - 鼠标在标准化设备坐标（NDC）中的二维位置，取值范围从 -1 到 +1。
 * @param param1 - 包含平面相关信息和相机对象的参数对象。
 * @param param1.planeNormal - 平面的法向量，用于定义平面的朝向。
 * @param param1.ptOnPlane - 平面上的一个点，与法向量共同确定平面的位置。
 * @param param1.camera - 用于创建射线的相机对象。
 * @returns 返回鼠标射线与指定平面的交点的三维坐标。
 */
export function mouseOnPlane(
  mouse: THREE.Vector2,
  { planeNormal, ptOnPlane, camera },
) {
  const raycaster = new THREE.Raycaster();
  raycaster.setFromCamera(mouse, camera); // 创建了一个新射线
  // 根据法向量和一个点来创建平面
  const plane = new THREE.Plane();
  plane.setFromNormalAndCoplanarPoint(planeNormal, ptOnPlane);
  const intersectPoint = new THREE.Vector3();
  raycaster.ray.intersectPlane(plane, intersectPoint);
  return intersectPoint;
}

export function makeId(info?: EntityInfo) {
  if (info == null) {
    return '';
  }
  const { dxid, type, idx } = info;
  let sp = '';
  switch (type) {
    case EntityPartType.point:
      sp = ':v';
      break;
    case EntityPartType.line:
      sp = ':e';
      break;
    case EntityPartType.face:
      sp = ':f';
      break;
  }
  return `${dxid}${sp}${idx}`;
}

export function makeIdNew(info?: EntityInfo) {
  if (info == null) {
    return '';
  }
  const { type, idx } = info;
  let sp = '';
  switch (type) {
    case EntityPartType.point:
      sp = 'POINT_';
      break;
    case EntityPartType.line:
      sp = 'LINE_';
      break;
    case EntityPartType.face:
      sp = 'FACE_';
      break;
    case EntityPartType.solid:
      sp = 'SOLID_';
      break;
  }
  return `${sp}${idx}`;
}

export function getEntInfoById(id: string) {
  if (id.includes(':v')) {
    const [dxid, idx] = id.split(':v');
    return { dxid: Number(dxid), type: EntityPartType.point, idx: Number(idx) };
  } else if (id.includes(':e')) {
    const [dxid, idx] = id.split(':e');
    return { dxid: Number(dxid), type: EntityPartType.line, idx: Number(idx) };
  } else {
    const [dxid, idx] = id.split(':f');
    return { dxid: Number(dxid), type: EntityPartType.face, idx: Number(idx) };
  }
}
/**
 * 通过后端返回的id，生成EntityInfo
 * @param idStr 后端id字符串，比如：POINT_1、LINE_2、FACE_3、SOLID_4、"2"
 * @returns 格式正确返回EntityInfo，否则返回undefined
 */
export function makeEntityInfo(
  idStr: GeometryItem | string,
): undefined | EntityInfo {
  const idSplited = idStr.split('_');
  if (idSplited.length === 1) {
    // 是object id，只需要dxid，进行相应的操作
    return { dxid: Number(idSplited[0]), type: undefined, idx: undefined };
  }
  if (idSplited.length !== 2) {
    nmAppErrorLog(`id:${idStr} format is illegality.`);
    return undefined;
  }
  const idx = Number(idSplited[1]);
  if (isNaN(idx)) {
    nmAppErrorLog(`id:${idx} must by number.`);
    return undefined;
  }
  let type: EntityPartType = undefined;
  switch (idSplited[0]) {
    case 'POINT':
      type = EntityPartType.point;
      break;
    case 'LINE':
      type = EntityPartType.line;
      break;
    case 'FACE':
      type = EntityPartType.face;
      break;
    case 'SOLID':
      type = EntityPartType.solid;
      break;
  }
  if (type === undefined) {
    nmAppErrorLog(`type:${idSplited[1]} is not support.`);
    return undefined;
  }
  return {
    dxid: undefined,
    type,
    idx,
  };
}

/**
 * 创建单个坐标轴，包含箭杆和箭头。
 *
 * @param origin - 坐标轴的起点向量，用于确定坐标轴的位置。
 * @param direction - 坐标轴的方向向量，用于确定坐标轴的朝向。
 * @param color - 坐标轴的颜色，用于设置箭杆和箭头的材质颜色。
 * @param axisLength - 坐标轴的总长度，包含箭杆和箭头的长度。
 * @param shaftRadius - 箭杆的半径，决定箭杆的粗细。
 * @param headLength - 箭头的长度，决定箭头在坐标轴上占据的长度。
 * @param headRadius - 箭头底部的半径，决定箭头的粗细。
 * @returns 返回一个包含箭杆和箭头的 THREE.Group 实例，表示创建好的坐标轴。
 */
export function createAxis(
  origin: THREE.Vector3,
  direction: THREE.Vector3,
  color: number,
  axisLength: number,
  shaftRadius: number,
  headLength: number,
  headRadius: number,
) {
  // 使箭头朝向指定方向
  const yAxis = new THREE.Vector3(0, 1, 0); // 圆锥默认沿 Y 轴指向

  const xAxis = yAxis.clone().cross(direction);
  // 判断yAsix是否和direction平行，如果平行则修改xAxis
  if (xAxis.length() < 10e-8) {
    if (direction.y > 0) {
      xAxis.set(0, 0, 1);
    } else {
      xAxis.set(0, 0, -1);
    }
  } else {
    xAxis.normalize();
  }
  // 计算
  const theta = Math.acos(
    yAxis.clone().dot(direction) / yAxis.length() / direction.length(),
  );
  // 创建一个 THREE.Group 实例，用于组合箭杆和箭头
  const axisGroup = new THREE.Group();

  // 1. 箭杆（圆柱体）
  // 创建一个圆柱体几何体作为箭杆，其高度为总长度减去箭头长度
  const endPoint = yAxis.clone().setLength(axisLength - headLength);
  const line = new CSLine({
    vertexes: [[0, 0, 0], endPoint.toArray()],
    lineWidth: shaftRadius,
    color,
    depthTest: true,
  });

  // 2. 箭头（圆锥体）
  // 创建一个圆锥体几何体作为箭头
  // 使用几何体和材质创建一个网格对象作为箭头
  const head = arrow(headLength, headRadius, color);

  // 将箭头放置在箭杆末端，通过方向向量和（总长度 - 箭头长度的一半）进行位置偏移
  head.position.copy(yAxis.clone().multiplyScalar(axisLength - headLength / 2));

  // 将箭杆和箭头添加到组合对象中
  axisGroup.add(line);
  axisGroup.add(head);
  axisGroup.setRotationFromAxisAngle(xAxis, theta);
  axisGroup.position.copy(origin);
  return axisGroup;
}
export function arrow(headLength: number, headRadius: number, color: number) {
  // 2. 箭头（圆锥体）
  // 创建一个圆锥体几何体作为箭头
  const headGeometry = new THREE.ConeGeometry(headRadius, headLength, 32);
  // 创建一个基础材质，设置颜色为传入的颜色
  const headMaterial = new THREE.MeshBasicMaterial({
    color,
    depthTest: true,
  });
  // 使用几何体和材质创建一个网格对象作为箭头
  return new THREE.Mesh(headGeometry, headMaterial);
}
/**
 * @description 根据向量获取两个点的点积 （判断正负）
 * @param startPoint 起始点
 * @param endPoint 结束点 (即鼠标正在移动的点)
 * @param vector 向量 （即方向）
 * @return 点积结果 1 即过点startPoint 在startPoint的上方，-1 即过点startPoint 在startPoint的下方
 * */
export function getDotProduct(
  startPoint: THREE.Vector3,
  endPoint: THREE.Vector3,
  vector: THREE.Vector3,
): number {
  return vector.clone().normalize().dot(endPoint.clone().sub(startPoint));
}

/**
 * @description 点到直线的投影点
 * @param point 点 (即鼠标位置)
 * @param lineStart 直线起点
 * @param lineVec 直线向量
 * */
export function pointProjectOnLine(point, lineStart, lineVec) {
  const v = point.clone().sub(lineStart);
  const distance = v.dot(lineVec.normalize());
  return lineStart.clone().add(lineVec.multiplyScalar(distance));
}
/**
 * 旋转 Vector2 向量
 * @param {THREE.Vector2} vec - 原始向量
 * @param {number} degree - 旋转角度（度）
 * @param {boolean} [inPlace=false] - 是否修改原向量 false 则返回新向量 true 则修改原向量
 * @returns {THREE.Vector2} 旋转后的向量
 *  eg: 使用示例
 *  const originalVec = new THREE.Vector2(0, 1);
 *  const rotatedVec = rotateVector2(originalVec, 30);
 *  console.log(rotatedVec); // ≈ [-0.5, 0.866]
 */
export function rotateVector2(
  vec: THREE.Vector2,
  degree: number,
  inPlace = false,
) {
  const target = inPlace ? vec : vec.clone();
  const radians = THREE.MathUtils.degToRad(degree);
  const cos = Math.cos(radians);
  const sin = Math.sin(radians);
  const x = target.x * cos - target.y * sin;
  const y = target.x * sin + target.y * cos;
  return target.set(x, y);
}
/**
 * 根据鼠标位置、边数据和相机信息计算相关参数。
 *
 * @param point - 鼠标位置的三维坐标，以三元组形式表示 [x, y, z]。
 * @param edgeData - 包含一系列三维向量的数组，代表边的顶点数据。
 * @param camera - 相机对象，可以是透视相机或正交相机。
 * @returns 包含计算得到的向量、当前点、符号、显示比例、显示长度、相机向上方向和比例的对象。
 */
export function getParams(
  point: THREE.Vector3Tuple,
  edgeData: THREE.Vector3[],
  camera: THREE.PerspectiveCamera | THREE.OrthographicCamera,
) {
  const pCurrent = new THREE.Vector3(...point); // 鼠标的点
  const { p, ind } = getCurveLastDistPoint(pCurrent, edgeData); // 最近点和最近点的索引
  const vec = new THREE.Vector3().subVectors(p[1], p[0]).normalize(); // 线段的方向
  const mindis = p[0].distanceTo(pCurrent); // 点到最近线段起始点的距离
  const { totalLength, lenByIndex } = getLineLen(edgeData, ind[0]); // 总长度和当前长度
  const ratio = MathTool.fixNumber((lenByIndex + mindis) / totalLength, 2); // 比例
  const len = lenByIndex + mindis; // 长度
  const ratioShow = MathTool.fixNumber(ratio > 0.5 ? 1 - ratio : ratio, 2); // 比例
  const showL = MathTool.fixNumber(ratio > 0.5 ? totalLength - len : len, 2);
  const upVec = new THREE.Vector3(camera.up.x, camera.up.y, camera.up.z); // 相机的向上方向
  const sign = ratio > 0.5 ? 1 : -1;
  return { vec, pCurrent, sign, ratioShow, showL, upVec, ratio };
}
/**
 * 计算屏幕所在平面的法线向量。
 *
 * @param camera - 相机对象，用于进行逆投影操作，将 NDC 坐标转换为世界坐标。
 * @returns 屏幕所在平面的法线向量，经过归一化处理。
 */
export function getScreenNormal(
  camera: THREE.PerspectiveCamera | THREE.OrthographicCamera,
) {
  // 获取屏幕四个角的NDC坐标
  const corners = [
    new THREE.Vector3(-1, -1, 0.5), // 左下
    new THREE.Vector3(1, -1, 0.5), // 右下
    new THREE.Vector3(0, 1, 0.5), // 上中
  ];
  // 转换为世界坐标射线方向
  const directions = corners.map((vec) => {
    vec.unproject(camera); // 逆投影
    return vec.sub(camera.position).normalize();
  });

  // 计算平面法线
  const v1 = directions[1].clone().sub(directions[0]);
  const v2 = directions[2].clone().sub(directions[0]);
  return new THREE.Vector3().crossVectors(v1, v2).normalize();
}

/**
 * 根据三个不共线的点计算平面的法向量。
 * 该方法接收一个包含至少三个点的数组，将这些点转换为 THREE.Vector3 类型，
 * 然后通过计算向量叉积得到平面的法向量，并对其进行归一化处理。
 * @param points - 包含 THREE.Vector3 或 Vector3Tuple 类型点的数组，用于计算法向量。
 * @returns 平面的归一化法向量；若传入的点少于 3 个，则返回 null。
 */
export function getNormalByPoints(
  points: (THREE.Vector3 | THREE.Vector3Tuple)[],
) {
  if (points.length < 2) {
    return null;
  }
  const [v1, v2, v3] = points.map((p) => {
    if (p instanceof THREE.Vector3) {
      return p;
    } else {
      return new THREE.Vector3(...p);
    }
  });
  const vec = new THREE.Vector3().subVectors(v1, v2).normalize();
  const vec2 = new THREE.Vector3().subVectors(v2, v3).normalize();
  return new THREE.Vector3().crossVectors(vec, vec2).normalize();
}

/**
 * 将一维数组形式的点坐标转换为三维坐标数组。
 * 输入数组中的每三个连续元素会被组合成一个三维坐标点。
 *
 * @param point - 包含点坐标的一维数组，每三个元素代表一个三维点的 x, y, z 坐标。
 * @returns 一个二维数组，每个子数组包含三个元素，表示一个三维坐标点。
 */
export function formatPoints(point: number[]) {
  const vs3 = [];
  for (let i = 0; i < point?.length; i += 3) {
    if (point[i] != null && point[i + 1] != null && point[i + 2] != null) {
      vs3.push([point[i], point[i + 1], point[i + 2]]);
    }
  }
  return vs3;
}

// 清除空的数组
export const removeEmptyArrays = (arr) => {
  return arr.map((item) => {
    if (Array.isArray(item.children)) {
      if (item.children.length) {
        item.children = removeEmptyArrays(item.children);
      } else {
        delete item.children;
      }
      return item;
    } else {
      return item;
    }
  });
};
